/**
 * 	 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *   @author Colin Redmond
 *   Copyright (C) 2011 Colin Redmond
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.cred.industries.platform.business.facade;

import javax.ws.rs.core.Response.Status;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cred.industries.core.exceptions.ApplicationException;
import com.cred.industries.core.transferObj.result.ReturnCodes;
import com.cred.industries.platform.business.objects.CustomerBO;
import com.cred.industries.platform.business.objects.SessionBO;
import com.cred.industries.platform.dao.ISessionDAO;
import com.cred.industries.platform.dao.SessionDAOFactory;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.oauth.server.OAuthServerRequest;
import com.sun.jersey.oauth.signature.OAuthParameters;
import com.sun.jersey.oauth.signature.OAuthSecrets;
import com.sun.jersey.oauth.signature.OAuthSignature;
import com.sun.jersey.oauth.signature.OAuthSignatureException;

/**
 * Facade for all interactions with sessions
 */
public class SessionFacade {
	private static final Logger logger = LoggerFactory.getLogger(SessionFacade.class);
	
	private static final String GUEST_ACCOUNT = "guest";

	/**
	 * Authenticates the user using oAuth, then creates a session and inserts it into the DB
	 * @param hc all information oAuth needs to authenticate the user
	 * @return the session created in the DB
	 * @throws ApplicationException
	 */
	public SessionBO createSession(HttpContext hc) {
		
		try {
		    // wrap incoming request for OAuth signature verification
		    OAuthServerRequest request = new OAuthServerRequest(hc.getRequest());
	
		    // get incoming OAuth parameters
		    OAuthParameters params = new OAuthParameters();
		    params.readRequest(request);
		    String consumerKey = params.getConsumerKey();
		    OAuthSecrets secrets = new OAuthSecrets();
		    
		    //get the customer associated with the session.
		    CustomerFacade custFacade = new CustomerFacade();
		    CustomerBO cust = custFacade.getCustomerByPersona(consumerKey);
		    if(cust == null) {
		    	throw new ApplicationException(Status.UNAUTHORIZED, ReturnCodes.UNKNOWN_ERROR, "unable to find Customer " + consumerKey);
		    }
		    	
		    //... set secrets based on consumer key and/or token in parameters ...
		    secrets.setConsumerSecret(cust.getPassword());
	    
		    //Authenticate the user using OAuth 
	        if(!OAuthSignature.verify(request, params, secrets)) {	
	        	throw new ApplicationException(Status.UNAUTHORIZED, ReturnCodes.UNKNOWN_ERROR, "failed to verify OAuthSignature " + cust.getCustomerId());
	        }
	        
        	//Now that they are authenticated create a new session
        	SessionBO sessionBO = new SessionBO();
        	sessionBO.createSession(cust.getCustomerId());
        	
        	//make sure the new sessionID is valid by checking in the DB to 
        	//see if a session with the same ID already exists
        	ISessionDAO sessionDAO = SessionDAOFactory.create();
        	while(sessionDAO.findSessionById(sessionBO.getSessionId()) != null) {
        		
        		logger.info("SessionID " + sessionBO.getSessionId() + " already in use. Creating another one");
        		sessionBO.createSession(cust.getCustomerId());
        	}
        	
        	//add the session to the DB
        	sessionDAO.insert(sessionBO);
        	
        	logger.info("SessionFacade created session for " + cust.getPersonaName() + " id " + cust.getCustomerId());
        	
        	return sessionBO;

	    } catch (OAuthSignatureException e) {
	    	logger.error("Exception " + e.toString() + " " + e.getMessage());
	    	e.printStackTrace();
	    	throw new ApplicationException(Status.UNAUTHORIZED, ReturnCodes.UNKNOWN_ERROR, "OAuthSignatureException ");
	    } 
	}
	
	/**
	 * removes a session from the DB. Can be used when a user logs off
	 * @param sessionId id of session we with to delete
	 * @throws ApplicationException
	 */
	public void deleteSession(String sessionId) {
		
		//find the session in the DB
		ISessionDAO sessionDAO = SessionDAOFactory.create();
		SessionBO sessionBO = null;
		sessionBO = sessionDAO.findSessionById(sessionId);
		
		//verify we have a session
		if(sessionBO == null) {
			throw new ApplicationException(Status.NOT_FOUND, ReturnCodes.UNKNOWN_ERROR, "failed to find session to delete " + sessionId);
		}
		
		//make people can only delete their own session
		if(!sessionBO.authorizedToModify()) {
			throw new ApplicationException(Status.UNAUTHORIZED, ReturnCodes.UNKNOWN_ERROR, "Not Authorized to delete session " + sessionBO.getCustomerId() + " session ID: " + sessionBO.getSessionId());
		}
		
		//delete the session from the Db
		if(!sessionDAO.delete(sessionBO))	{
			throw new ApplicationException(Status.NOT_FOUND, ReturnCodes.UNKNOWN_ERROR, "failed to delete session from " + sessionBO.getCustomerId() + " session ID: " + sessionBO.getSessionId());
		}
		
		logger.info("SessionFacade deleted session for " + sessionId + " id " + sessionBO.getCustomerId());
	}
	
	/**
	 * Using the session ID it will try and find the matching session in the DB. 
	 * It will then return the customer associated with the session
	 * If one isn't found, it will return the guest account
	 * This will also update the last Request time to the current time
	 * @param sessionId id for the Session in the DB
	 * @return customer associated with the session
	 */
	public CustomerBO authenticateUserbySessionId(String sessionId) {
		
		CustomerBO cust = null;
		SessionBO sessionBO = validateSession(sessionId);
		if(sessionBO != null) {
			
			//update the last request time to now.
			ISessionDAO sessionDAO = SessionDAOFactory.create();
			sessionBO.setLastRequest(new DateTime(DateTimeZone.UTC));
			sessionDAO.update(sessionBO);
			
			cust = getAssociatedCustomer(sessionBO);
		} else { 
			//if we can't find the session return the guest account. We will log the user in with this account
			logger.warn("authenticateUserbySession can not find a valid session for session " + sessionId + "logging in as guest");
			CustomerFacade custF = new CustomerFacade();
			cust = custF.getCustomerByPersona(GUEST_ACCOUNT);
		}
		
		return cust;
	}
	
	/**
	 * validates that we have a session with a matching key in the DB
	 * @param sessionId id of session we with to validate
	 * @return the sessionBo that matches the Session ID
	 */
	protected SessionBO validateSession(String sessionId) {
		
		//find the session in the DB
		SessionBO sessionBO = null; 
		ISessionDAO sessionDAO = SessionDAOFactory.create();
		sessionBO = sessionDAO.findSessionById(sessionId);
		
		//verify we have a session
		if(sessionBO == null) {
			logger.error("validateSession can not find session for " + sessionId);
			return null;
		}
		
		//validate that the session is still valid
		if(!sessionBO.validate()) {
			logger.error("validateSession can not validate session " + sessionId);
			return null;
		}
		
		return sessionBO;
		
	}
	
	/**
	 * Given a session returns the Customer associated with that session
	 * @param sessionBO of session we are to find the customer for
	 * @return the customer that is authenticated with the session
	 */
	protected CustomerBO getAssociatedCustomer(SessionBO session) {
		
		//find the customer associated wit the session
		CustomerFacade custFacade = new CustomerFacade();
		CustomerBO cust = custFacade.getCustomerById(session.getCustomerId());
		if(cust == null) {
			logger.warn("session " + session.getSessionId() + " does not have a customer " + session.getCustomerId());
			return null;
		}
		
		return cust;
	}
}
